import unittest
import sys

sys.path.append('../..')


class TestSystem(list):

	def add_group(self, group):
		self.append(group)


class TestController:
	group = None
	
	def __call__(self, td, group):
		self.group = group
		self.time_delta = td

class TestUnbindingController:
	"""Controller that unbinds itself when invoked"""
	group = None
	
	def __call__(self, td, group):
		self.group = group
		group.unbind_controller(self)

class TestBindingController:
	"""Controller that binds another controller when invoked"""
	group = None

	def __init__(self, ctrl):
		self.ctrl = ctrl
	
	def __call__(self, td, group):
		self.group = group
		group.bind_controller(self.ctrl)


class TestRenderer:
	group = None
	drawn = False

	def set_group(self, group):
		self.group = group
	
	def draw(self):
		self.drawn = True


class TestParticle:
	velocity = (0,0,0)


class GroupTest(unittest.TestCase):

	def test_group_system(self):
		import particle
		from particle import ParticleGroup
		# By default the group should be added to the global system
		group = ParticleGroup()
		self.failUnless(group in particle.default_system)
		particle.default_system.add_group(group)

		# We should be able to override the system by argument
		test_system = TestSystem()
		group = ParticleGroup(system=test_system)
		self.failUnless(group in test_system)
		self.failIf(group in particle.default_system)

		# If the global system is overridden, the group should respect that
		original_system = particle.default_system
		try:
			particle.default_system = test_system
			group = ParticleGroup()
			self.failUnless(group in test_system)
		finally:
			particle.default_system = original_system

		# If None is specified for system, group is not added
		group = ParticleGroup(system=None)
		self.failIf(group in particle.default_system)
		self.failIf(group in test_system)
	
	def test_bind_controllers(self):
		from particle import ParticleGroup
		ctrl1 = TestController()
		ctrl2 = TestController()
		ctrl3 = TestController()
		ctrl4 = TestController()
		group = ParticleGroup()
		self.failIf(group.controllers)

		# Can bind controllers in constructor and after
		group = ParticleGroup(controllers=(ctrl1, ctrl2))
		self.assertEqual(tuple(group.controllers), (ctrl1, ctrl2))
		group.bind_controller(ctrl3, ctrl4)
		self.assertEqual(tuple(group.controllers), (ctrl1, ctrl2, ctrl3, ctrl4))

		'''
		# Can bind controllers at the class level and after
		class MyGroup(ParticleGroup):
			controllers = (ctrl1, ctrl2)
		group = MyGroup(controllers=[ctrl3])
		self.assertEqual(tuple(group.controllers), (ctrl1, ctrl2, ctrl3))
		group.bind_controller(ctrl4)
		self.assertEqual(tuple(group.controllers), (ctrl1, ctrl2, ctrl3, ctrl4))
		'''
	
	def test_unbind_controllers(self):
		from particle import ParticleGroup
		ctrl1 = TestController()
		ctrl2 = TestController()
		group = ParticleGroup(controllers=(ctrl1, ctrl2))
		self.failUnless(ctrl1 in group.controllers)
		self.failUnless(ctrl2 in group.controllers)
		group.unbind_controller(ctrl1)
		self.failUnless(ctrl1 not in group.controllers)
		self.failUnless(ctrl2 in group.controllers)
		group.unbind_controller(ctrl2)
		self.failUnless(ctrl1 not in group.controllers)
		self.failUnless(ctrl2 not in group.controllers)
		self.assertRaises(ValueError, group.unbind_controller, ctrl1)
	
	def test_modify_controllers_during_update(self):
		from particle import ParticleGroup
		ctrl1 = TestController()
		ctrl2 = TestUnbindingController()
		ctrl4 = TestController()
		ctrl3 = TestBindingController(ctrl4)
		group = ParticleGroup(controllers=(ctrl1, ctrl2, ctrl3))
		self.assertEqual(len(group.controllers), 3)
		self.failUnless(ctrl1 in group.controllers)
		self.failUnless(ctrl2 in group.controllers)
		self.failUnless(ctrl3 in group.controllers)
		self.failUnless(ctrl4 not in group.controllers)
		group.update(1)
		self.failUnless(ctrl1.group is group)
		self.failUnless(ctrl2.group is group)
		self.failUnless(ctrl3.group is group)
		self.failUnless(ctrl4.group is None)
		self.assertEqual(len(group.controllers), 3)
		self.failUnless(ctrl1 in group.controllers)
		self.failUnless(ctrl2 not in group.controllers)
		self.failUnless(ctrl3 in group.controllers)
		self.failUnless(ctrl4 in group.controllers)

	def test_set_renderer(self):
		from particle import ParticleGroup
		renderer = TestRenderer()
		group = ParticleGroup()
		self.assertEqual(group.renderer, None)
		self.assertEqual(renderer.group, None)

		# Can set renderer after init
		group.set_renderer(renderer)
		self.assertEqual(group.renderer, renderer)
		self.assertEqual(renderer.group, group)

		# Can set renderer at init
		group2 = ParticleGroup(renderer=renderer)
		self.assertEqual(group2.renderer, renderer)
		self.assertNotEqual(renderer.group, group)
		self.assertEqual(renderer.group, group2)

		# Can set renderer again
		renderer2 = TestRenderer()
		group.set_renderer(renderer2)
		self.assertEqual(group.renderer, renderer2)
		self.assertEqual(renderer2.group, group)
	
	def test_new_particle(self):
		from particle import ParticleGroup, Particle
		group = ParticleGroup()
		self.assertEqual(len(group), 0)
		self.assertEqual(group.new_count(), 0)
		p1 = Particle(age=1)
		p2 = Particle(age=2)
		group.new(p1)
		self.assertEqual(len(group), 0)
		self.assertEqual(group.new_count(), 1)
		self.failIf(list(group))
		group.new(p2)
		self.assertEqual(len(group), 0)
		self.assertEqual(group.new_count(), 2)
		self.failIf(list(group))
		group.update(0) # incorporate new particles
		self.assertEqual(len(group), 2)
		self.assertEqual(group.new_count(), 0)
		particles = list(group)
		self.assertEqual(len(particles), 2)
		self.assertEqual(particles[0].age, 1)
		self.assertEqual(particles[1].age, 2)
		return group, particles
	
	def test_particle_attrs(self):
		from particle import ParticleGroup
		p = TestParticle()
		p.position=(1,2,3)
		p.velocity=(4,5,6)
		p.color=(7,8,9,10)
		p.size=(11,12,13)
		p.up=(-1,-2,-3)
		p.rotation=(-4,-5,-6)
		p.age=111
		p.mass=2

		group = ParticleGroup()
		newp = group.new(p)
		self.assertEqual(tuple(newp.position), p.position)
		self.assertEqual(tuple(newp.velocity), p.velocity)
		self.assertEqual(tuple(newp.color), p.color)
		self.assertEqual(tuple(newp.size), p.size)
		self.assertEqual(tuple(newp.up), p.up)
		self.assertEqual(tuple(newp.rotation), p.rotation)
		self.assertEqual(newp.age, p.age)
		self.assertEqual(newp.mass, p.mass)
	
	def test_particle_vector_swizzle(self):
		from particle import ParticleGroup
		group = ParticleGroup()
		newp = group.new(TestParticle())
		self.assertEqual(tuple(newp.velocity), (0,0,0))
		self.assertEqual(newp.velocity.x, 0)
		self.assertEqual(newp.velocity.y, 0)
		self.assertEqual(newp.velocity.z, 0)
		newp.velocity.x = 2
		newp.velocity.y = -2
		newp.velocity.z = 1
		self.assertEqual(tuple(newp.velocity), (2,-2,1))
		self.assertEqual(newp.velocity.x, 2)
		self.assertEqual(newp.velocity.y, -2)
		self.assertEqual(newp.velocity.z, 1)
		newp.velocity = (3,4,5)
		self.assertEqual(tuple(newp.velocity), (3,4,5))
		self.assertEqual(newp.velocity.x, 3)
		self.assertEqual(newp.velocity.y, 4)
		self.assertEqual(newp.velocity.z, 5)

		self.assertEqual(tuple(newp.color), (0,0,0,0))
		self.assertEqual(newp.color.r, 0)
		self.assertEqual(newp.color.g, 0)
		self.assertEqual(newp.color.b, 0)
		self.assertEqual(newp.color.a, 0)
		newp.color.r = 1
		newp.color.g = -2
		newp.color.b = 3
		newp.color.a = -1
		self.assertEqual(tuple(newp.color), (1,-2,3,-1))
		self.assertEqual(newp.color.r, 1)
		self.assertEqual(newp.color.g, -2)
		self.assertEqual(newp.color.b, 3)
		self.assertEqual(newp.color.a, -1)
		newp.color = (5,4,3,2)
		self.assertEqual(tuple(newp.color), (5,4,3,2))
		self.assertEqual(newp.color.r, 5)
		self.assertEqual(newp.color.g, 4)
		self.assertEqual(newp.color.b, 3)
		self.assertEqual(newp.color.a, 2)
		newp.color = (6,5,4) # alpha defaults to 1
		self.assertEqual(tuple(newp.color), (6,5,4,1))
		self.assertEqual(newp.color.r, 6)
		self.assertEqual(newp.color.g, 5)
		self.assertEqual(newp.color.b, 4)
		self.assertEqual(newp.color.a, 1)
	
	def test_mass_new_particles(self):
		from particle import ParticleGroup
		count = 12345
		group = ParticleGroup()
		p = TestParticle()
		for i in xrange(count):
			p.mass = i
			newp = group.new(p)
			self.assertEqual(newp.mass, p.mass)

		group.update(0)
		piter = iter(group)
		for i in xrange(count):
			newp = piter.next()
			self.assertEqual(newp.mass, i)

		self.assertEqual(len(group), count)
		self.assertEqual(group.killed_count(), 0)
		self.assertRaises(StopIteration, piter.next)
	
	def test_particle_ref_invalidation(self):
		from particle.group import InvalidParticleRefError
		group, particles = self.test_new_particle()
		piter = iter(group)
		self.assertEqual(piter.next().age, particles[0].age)
		group.update(0) # Invalidates particle references and iter
		self.assertRaises(InvalidParticleRefError, getattr, particles[0], 'age')
		self.assertRaises(InvalidParticleRefError, getattr, particles[1], 'age')
		self.assertRaises(InvalidParticleRefError, piter.next)
	
	def test_kill_particle(self):
		group, particles = self.test_new_particle()
		group.kill(particles[0])
		self.assertEqual(len(group), 1)
		group.kill(particles[1])
		self.assertEqual(len(group), 0)
		self.assertEqual(list(group), [])
		# group.kill() only accepts particle ref objects
		p3 = TestParticle()
		self.assertRaises(TypeError, group.kill, p3)
		# update should reclaim killed particles
		self.assertEqual(group.killed_count(), 2)
		group.update(0)
		self.assertEqual(group.killed_count(), 0)
		self.assertEqual(len(group), 0)

	def test_len(self):
		from particle import ParticleGroup
		group = ParticleGroup()
		self.assertEqual(len(group), 0)
		group.new(TestParticle())
		self.assertEqual(len(group), 0)
		group.new(TestParticle())
		self.assertEqual(len(group), 0)
		# New particles become visible on update
		group.update(0)
		self.assertEqual(len(group), 2)
	
	def test_update(self):
		group, particles = self.test_new_particle()
		ctrl1 = TestController()
		ctrl2 = TestController()
		group.bind_controller(ctrl1, ctrl2)
		group.update(0.33)
		self.failUnless(ctrl1.group is group)
		self.assertAlmostEqual(ctrl1.time_delta, 0.33)
		self.failUnless(ctrl2.group is group)
		self.assertAlmostEqual(ctrl2.time_delta, 0.33)
			
	def test_draw(self):
		group, particles = self.test_new_particle()
		renderer = TestRenderer()
		self.assertFalse(renderer.drawn)
		group.set_renderer(renderer)
		group.draw()
		self.assertTrue(renderer.drawn)


if __name__=='__main__':
	unittest.main()
